import datetime
import html
import os
import platform
import logging
import json
import re
import shlex
import subprocess
import multiprocessing
import sys
import threading
import urllib

import yaml

LOG = logging.getLogger(__name__)
process_lock = multiprocessing.Lock()
thread_lock = threading.Lock()


def get_url_host_port(url: str):
    if '//' in url:
        protocol = url.split('//')[0]
        base_url = url.split('//')[-1]
    else:
        protocol = ''
        base_url = url
    [host, port] = base_url.split(':')
    port = int(port)
    return protocol, host, port


def get_now_day():
    return datetime.datetime.now().strftime("%Y-%m-%d")


def get_now_day_now_time():
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def set_env_abs_path(here, check_flag='dir'):
    import dotenv
    dotenv.load_dotenv(dotenv.find_dotenv(), override=True)
    env_dict = dotenv.dotenv_values()
    for key, value in env_dict.items():
        if check_flag not in key.lower():
            continue
        if not os.path.isabs(value):
            value = os.path.join(here, value)
            value = os.path.abspath(value)
            env_dict[key] = value
            os.environ[key] = value


def write_file(path, content, json_flag=False):
    try:
        with open(path, 'w') as f:
            if json_flag:
                json.dump(content, f, indent=4)
            else:
                f.write(content)
    except Exception as e:
        err_msg = 'write {}, error={}, content={}'.format(path, e, content)
        LOG.error(err_msg)
        raise Exception(err_msg)


def read_file(path, json_flag=False):
    try:
        with open(path, 'r', encoding='UTF-8') as f:
            if json_flag:
                return json.load(f)
            else:
                return f.read()
    except Exception as e:
        err_msg = 'read {}, error={}'.format(path, e)
        LOG.error(err_msg)
        raise Exception(err_msg)


def get_execute_cmd_result(cmd: str, **kwargs):
    args = shlex.split(cmd)
    cmd_err = "execute cmd='{}' failed".format(cmd)
    try:
        res = subprocess.check_output(args, stderr=subprocess.STDOUT,
                                      universal_newlines=True,
                                      encoding="utf-8", **kwargs)
    except subprocess.TimeoutExpired as e:
        err = "{}, timeout={} seconds".format(cmd_err, kwargs.get('timeout'))
        LOG.error(err)
        raise Exception(err)
    except subprocess.CalledProcessError as e:
        err = "{}, code={}, err={}".format(cmd_err, e.returncode, e.output.strip())
        LOG.error(err)
        raise Exception(err)
    except Exception as e:
        err = "{}, err={}".format(cmd_err, str(e))
        LOG.error(err)
        raise Exception(err)
    else:
        msg = "cmd={}, return_code=0".format(cmd)
        LOG.debug(msg)
        # return bytes.decode(p).strip()
        return res


def get_platform_name():
    name = platform.system()
    name = name.lower()
    if name not in ['windows', 'linux']:
        err_msg = 'not support this platform={}'.format(name)
        LOG.error(err_msg)
        raise Exception(err_msg)
    LOG.debug('now platform system is {}'.format(name))
    return name


def read_yaml(path):
    try:
        with open(path, 'r', encoding='utf-8') as f:
            result = f.read()
        yaml_dict = yaml.safe_load(result)
    except Exception as e:
        err_msg = 'read {} failed, err={}'.format(path, str(e))
        LOG.warning(err_msg)
        raise Exception(err_msg)
    else:
        return yaml_dict


def make_dirs_if_not_exist(path: str):
    path = os.path.abspath(path)
    with process_lock:
        with thread_lock:
            if not os.path.exists(path):
                os.makedirs(path)


def run_self_func_args(func, args: str):
    func_name = getattr(func, '__name__')
    param_num = func.__code__.co_argcount
    if param_num < 1:
        err_msg = "run_self_func_args: {} must be self.func".format(func_name)
        LOG.error(err_msg)
        raise Exception(err_msg)
    param_num -= 1
    err_msg = '{} args number is not correct, args={}, param_num={}'.format(func_name, args, param_num)
    if args and param_num > 0:
        if not isinstance(args, str):
            return func(args)
        args_list = args.split('|')
        if param_num != len(args_list):
            LOG.error(err_msg)
            raise Exception(err_msg)
        elif param_num == 1:
            return func(args)
        else:
            return func(*args_list)
    elif not args and param_num == 0:
        return func()
    else:
        LOG.error(err_msg)
        raise Exception(err_msg)


def get_bounds_center(coord):
    x_point = (int(coord[2]) - int(coord[0])) / 2.0 + int(coord[0])
    y_point = (int(coord[3]) - int(coord[1])) / 2.0 + int(coord[1])
    return x_point, y_point


def check_str_equal(str1: str, str2: str, match_case=True) -> bool:
    if match_case:
        return str1 == str2
    else:
        return str1.lower() == str2.lower()


def get_os_environ_config_value(key, what_type=str, default=None):
    if what_type not in [int, float, str]:
        raise Exception('what_type not support {}'.format(what_type))
    value = os.getenv(key, default)
    value = what_type(value) if not isinstance(value, what_type) else value
    return value


def unescape(string):
    string = urllib.parse.unquote(string)
    quoted = html.unescape(string).encode(sys.getfilesystemencoding()).decode('utf-8')
    #转成中文
    return re.sub(r'%u([a-fA-F0-9]{4}|[a-fA-F0-9]{2})', lambda m: chr(int(m.group(1), 16)), quoted)